
#include <nxos.h>
#include "netc.h"

#define NET_BUF_SIZE 4096

static int NetClientTls = -1;

static inline NX_IpcClient *Get_NET_Ipc(void)
{
    NX_Error err;

    NX_IpcClient *client = NX_TlsGetValue(NetClientTls);
    if (client == NX_NULL)
    {
        client = NX_MemAlloc(sizeof(NX_IpcClient));
        if (!client) {
            NX_Printf("ipc client alloc failed!\n");
            return client;
        }
        err = NX_TlsSetValue(NetClientTls, client);
        if (err != NX_EOK)
        {
            NX_Printf("ipc client tls set value failed!\n");
            NX_MemFree(client);
            return NX_NULL;
        }

        err = NX_IpcConnect("net", Get_NET_Ipc(), NET_BUF_SIZE);
        if (err != NX_EOK)
        {
            NX_Printf("ipc client connect to server failed!\n");
            NX_TlsSetValue(NetClientTls, NX_NULL);
            NX_MemFree(client);
            return NX_NULL;
        }
    }
    return client;
}

void InitNET_Client(void)
{
    /* alloc net tls */
    NetClientTls = NX_TlsAlloc();
    if (NetClientTls < 0)
    {
        NX_Printf("Alloc net tls failed!\n");
        NX_ThreadExit(0);
    }
}

NX_INITCALL(InitNET_Client);

/* TODO: free tls on thread exit */

NX_Solt NET_Socket(int domain, int type, int protocol)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg) {
        NX_ErrorSet(NX_ENOMEM);
        return NX_SOLT_INVALID_VALUE;
    }
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_SOCKET;

    struct NET_SocketArgs *args = &req->net_socket_args;
    args->err = NX_EOK;
    args->domain = domain;
    args->type = type;
    args->protocol = protocol;

    int ret = NX_IpcCall(Get_NET_Ipc(), msg);
    NX_ErrorSet(args->err);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Error NET_Close(NX_Solt socket)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg)
        return NX_ENOMEM;
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_CLOSE;

    struct NET_CloseArgs *args = &req->net_close_args;
    args->socket = socket;

    NX_Error ret = NX_IpcCall(Get_NET_Ipc(), msg);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Error NET_Bind(NX_Solt socket, struct sockaddr *addr, socklen_t addrlen)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg)
        return NX_ENOMEM;
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_BIND;

    struct NET_BindArgs *args = &req->net_bind_args;
    args->socket = socket;
    args->addr = *addr;
    args->addrlen = addrlen;

    NX_Error ret = NX_IpcCall(Get_NET_Ipc(), msg);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Error NET_Listen(NX_Solt socket, int backlog)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg)
        return NX_ENOMEM;
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_LISTEN;

    struct NET_ListenArgs *args = &req->net_listen_args;
    args->socket = socket;
    args->backlog = backlog;

    NX_Error ret = NX_IpcCall(Get_NET_Ipc(), msg);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Error NX_Connect(NX_Solt socket, struct sockaddr *addr, socklen_t addrlen)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg)
        return NX_ENOMEM;
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_CONNECT;

    struct NET_ConnectArgs *args = &req->net_connect_args;
    args->socket = socket;
    args->addr = *addr;
    args->addrlen = addrlen;

    NX_Error ret = NX_IpcCall(Get_NET_Ipc(), msg);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Solt NET_Accept(NX_Solt socket, struct sockaddr *addr, socklen_t *addrlen)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg) {
        NX_ErrorSet(NX_ENOMEM);
        return NX_SOLT_INVALID_VALUE;
    }
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_ACCEPT;

    struct NET_AcceptArgs *args = &req->net_accept_args;
    args->socket = socket;
    args->err = NX_EOK;

    NX_Solt ret = NX_IpcCall(Get_NET_Ipc(), msg);

    *addr = args->addr;
    *addrlen = args->addrlen;
    NX_ErrorSet(args->err);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_SSize NET_Send(NX_Solt socket, void *buf, int len, int flags)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg) {
        NX_ErrorSet(NX_ENOMEM);
        return -1;
    }
    if (!len) {
        NX_ErrorSet(NX_EINVAL);
        return 0;
    }
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_SEND;

    struct NET_SendArgs *args = &req->net_send_args;
    args->err = NX_EOK;
    args->socket = socket;
    NX_MemCopy(args->buf, buf, len);
    args->len = len;
    args->flags = flags;
    
    NX_SSize ret = NX_IpcCall(Get_NET_Ipc(), msg);

    NX_ErrorSet(args->err);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_SSize NET_Recv(NX_Solt socket, void *buf, int len, int flags)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg) {
        NX_ErrorSet(NX_ENOMEM);
        return -1;
    }
    if (!len) {
        NX_ErrorSet(NX_EINVAL);
        return 0;
    }
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_RECV;

    struct NET_RecvArgs *args = &req->net_recv_args;
    args->err = NX_EOK;
    args->socket = socket;
    args->len = len;
    args->flags = flags;
    
    NX_SSize ret = NX_IpcCall(Get_NET_Ipc(), msg);

    if (ret > 0)
    {
        NX_MemCopy(buf, args->buf, ret);
    }

    NX_ErrorSet(args->err);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Error NET_Shutdown(NX_Solt socket, int how)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg) {
        NX_ErrorSet(NX_ENOMEM);
        return NX_ENOMEM;
    }

    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_SHUTDOWN;

    struct NET_ShutdownArgs *args = &req->net_shutdown_args;
    args->socket = socket;
    args->how = how;
    
    NX_Error ret = NX_IpcCall(Get_NET_Ipc(), msg);

    NX_ErrorSet(ret);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Error NET_Control(NX_Solt socket, long cmd, void *argp)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg) {
        NX_ErrorSet(NX_ENOMEM);
        return NX_ENOMEM;
    }
    if (!argp) {
        NX_ErrorSet(NX_EINVAL);
        return 0;
    }
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_CONTROL;

    struct NET_ControlArgs *args = &req->net_control_args;
    args->socket = socket;
    args->cmd = cmd;
    args->argp = argp ? *(unsigned long *)argp : 0;
    
    NX_Error ret = NX_IpcCall(Get_NET_Ipc(), msg);

    if (argp)
    {
        *(unsigned long *)argp = args->argp;
    }

    NX_ErrorSet(ret);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_SSize NET_Sendto(NX_Solt socket, void *buf, int len, int flags,
    struct sockaddr *addr, socklen_t addrlen)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg) {
        NX_ErrorSet(NX_ENOMEM);
        return -1;
    }
    if (!len || !addr) {
        NX_ErrorSet(NX_EINVAL);
        return 0;
    }
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_SENDTO;

    struct NET_SendtoArgs *args = &req->net_sendto_args;
    args->err = NX_EOK;
    args->socket = socket;
    NX_MemCopy(args->buf, buf, len);
    args->size = len;
    args->flags = flags;
    args->to = *addr;
    args->tolen = addrlen;
    
    NX_SSize ret = NX_IpcCall(Get_NET_Ipc(), msg);

    NX_ErrorSet(args->err);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_SSize NET_Recvfrom(NX_Solt socket, void *buf, int len, int flags,
    struct sockaddr *addr, socklen_t *addrlen)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg) {
        NX_ErrorSet(NX_ENOMEM);
        return -1;
    }
    if (!len || !addr) {
        NX_ErrorSet(NX_EINVAL);
        return 0;
    }
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_RECVFROM;

    struct NET_RecvfromArgs *args = &req->net_recvfrom_args;
    args->err = NX_EOK;
    args->socket = socket;
    args->size = len;
    args->flags = flags;

    NX_SSize ret = NX_IpcCall(Get_NET_Ipc(), msg);

    if (ret > 0)
    {
        NX_MemCopy(buf, args->buf, ret);      
        if (addr) {
            *addr = args->from;
        }
        if (addrlen) {
            *addrlen = args->fromlen;
        }
    }

    NX_ErrorSet(args->err);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Error NET_Setsockopt(NX_Solt socket, int level, int optname, const void *optval, socklen_t optlen)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg) {
        NX_ErrorSet(NX_ENOMEM);
        return NX_ENOMEM;
    }

    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_SETSOCKOPT;

    struct NET_SetsockoptArgs *args = &req->net_setsockopt_args;
    args->socket = socket;
    args->level = level;
    args->optname = optname;
    args->optlen = optlen;
    if (optval) {
        NX_MemCopy(args->optval, optval, optlen);
    }
    
    NX_Error ret = NX_IpcCall(Get_NET_Ipc(), msg);
    NX_ErrorSet(ret);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Error NET_Getsockopt(NX_Solt socket, int level, int optname, const void *optval, socklen_t *optlen)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_NET_Ipc(), GET_NET_REQUEST_SIZE(socket), 0);
    if (!msg) {
        NX_ErrorSet(NX_ENOMEM);
        return NX_ENOMEM;
    }
    NET_IpcReq *req = (NET_IpcReq *)NX_IpcGetMsgData(msg);
    req->type = NET_REQ_GETSOCKOPT;

    struct NET_GetsockoptArgs *args = &req->net_getsockopt_args;
    args->socket = socket;
    args->level = level;
    args->optname = optname;
    if (optlen) {
        args->optlen = *optlen;
    }
    NX_Error ret = NX_IpcCall(Get_NET_Ipc(), msg);

    if (optval) {
        NX_MemCopy(optval, args->optval, args->optlen);
    }
    if (optlen) {
        *optlen = args->optlen;
    }

    NX_ErrorSet(ret);
    NX_IpcDestroyMsg(msg);
    return ret;
}
